﻿using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace IsreversI.GameLogic
{
    /// <summary>
    /// Model representing a Reversi board.
    /// </summary>
    class BoardModel
    {
        #region member variables
        private TileState[,] board;
        public TurnState CurrentState;

        public static readonly IntVector2[] Directions =
        {
            new IntVector2( 0,  1),
            new IntVector2( 1,  0),
            new IntVector2( 1,  1),
            new IntVector2( 0, -1),
            new IntVector2(-1,  0),
            new IntVector2(-1, -1),
            new IntVector2(-1,  1),
            new IntVector2( 1, -1)
        };

        private Semaphore boardLocked;
        #endregion

        #region events
        public event BoardModelAttachment.BoardEventHandler ModelLocked;
        public event BoardModelAttachment.BoardEventHandler ModelUnlocked;
        public event BoardModelAttachment.BoardEventHandler StonePlayed;
        public event BoardModelAttachment.BoardEventHandler StoneFlipped;
        public event BoardModelAttachment.BoardEventHandler StoneRemoved;
        #endregion

        #region properties
        public Size BoardSize
        {
            get
            {
                return new Size(board.GetLength(0), board.GetLength(1));
            }
        }

        public TileState this[Point p]
        {
            get
            {
                return board[p.X, p.Y];
            }
        }

        public TileState this[int x, int y]
        {
            get
            {
                return board[x, y];
            }
        }
        #endregion

        #region constructors
        public BoardModel(Size dimensions)
        {
            this.boardLocked = new Semaphore(1, 1);
            this.TryLockModel(-1);
            this.board = new TileState[dimensions.Width, dimensions.Height];

            this.CurrentState = TurnState.RedTurn;
            for (int xCounter = 0; xCounter < dimensions.Width; ++xCounter)
                for (int yCounter = 0; yCounter < dimensions.Height; ++yCounter)
                {
                    this.board[xCounter, yCounter] = TileState.Empty;
                }
            // Find halfway point, rounding up.
            int xHalfWay = (dimensions.Width % 2) + dimensions.Width / 2;
            int yHalfWay = (dimensions.Height % 2) + dimensions.Height / 2;

            // Place initial pieces
            this.board[xHalfWay  , yHalfWay  ] = TileState.Blue;
            this.board[xHalfWay-1, yHalfWay  ] = TileState.Red;
            this.board[xHalfWay  , yHalfWay-1] = TileState.Red;
            this.board[xHalfWay-1, yHalfWay-1] = TileState.Blue;
            this.UnlockModel();
        }
        #endregion

        #region public methods
        #region conditions
        #endregion

        #region effects
        public bool TryLockModel(int msTimeout)
        {
            if (!this.boardLocked.WaitOne(msTimeout))
                return false;
            if (this.ModelLocked != null)
                this.ModelLocked(this, new BoardEventArgs(this));
            return true;
        }

        public void UnlockModel()
        {
            this.boardLocked.Release();
            if (this.ModelUnlocked != null)
                this.ModelUnlocked(this, new BoardEventArgs(this));
        }

        /// <summary>
        /// Set a stone of the given colour on the given position, and raise the
        /// associated event.
        /// </summary>
        /// <param name="position">Point on which to place stone.</param>
        /// <param name="colour">Colour of the player who will be placing it.</param>
        public void PlaceStoneAt(Point position, PlayerColour colour)
        {
            BoardEventArgs bea = new BoardEventArgs(this);
            bea.OldState = this[position];
            bea.NewState = (TileState)colour;
            this.board[position.X, position.Y] = bea.NewState;
            bea.Point = position;
            if (this.StonePlayed != null)
                this.StonePlayed(this, bea);
        }

        /// <summary>
        /// If it is present, remove the stone at the given position.  In any case,
        /// raise an event notifying everyone that the point is now empty.
        /// </summary>
        /// <param name="position">Position to remove stone from.</param>
        public void RemoveStoneAt(Point position)
        {
            BoardEventArgs bea = new BoardEventArgs(this);
            bea.OldState = this[position];
            bea.NewState = TileState.Empty;
            this.board[position.X, position.Y] = bea.NewState;
            bea.Point = position;
            if (this.StoneRemoved!= null)
                this.StoneRemoved(this, bea);
        }

        public void FlipPieceAt(Point position)
        {
            BoardEventArgs bea = new BoardEventArgs(this);
            bea.OldState = this[position];
            bea.NewState = this[position].Flipped();
            this.board[position.X, position.Y] = bea.NewState;
            bea.Point = position;
            if (this.StoneFlipped != null)
                this.StoneFlipped(this, bea);
        }
        #endregion
        #endregion

        #region private methods
        #endregion
    }
}
